" vim:ts=2:sw=2:et:
" 自动补全功能测试, 针对 Lua

" 补全函数
fun! hproof#luacomp#CompleteFunc(findstart, base)
  if a:findstart
    let line = getline('.')
    if strlen(line)
      let start = col('.') - 1
      " 标识符
      if line[start-1] =~ '[.:]\|\k'
        while start > 0 && line[start-1] =~ '[.:]\|\k'
          let start -= 1
        endwhile
        return start
      endif
      " 参数表
      let leader = line[0 : start]
      let idx = match(leader, '([^)]*$' )
      if idx > 0
        return idx
      endif
    endif
    return -1
  else
    let result = [ a:base ]
    " 参数补全
    if a:base =~ '('
      call s:addParams(result, a:base)
    " 标识符补全
    else
      call s:addIdentifier(result, a:base)
    endif
    return result
  endif
endfun

" 参数补全
fun! s:addParams(result, base)
  let line = getline('.')
  let end = col('.')-1
  let start = l:end
  while start > 0 && line[start-1] =~ '[.:]\|\k'
    let start -= 1
  endwhile
  let name = line[l:start : l:end]
  let result = []

  " 精确匹配 name, menu='filename'
  let list = taglist( '^' . escape(name, '.:') . '$' )
  call filter(list, " v:val.kind == 'f' " )
  for m in list 
    let params = substitute(m.cmd, '^.*\((.*)\).*$', '\1', '')
    call add( a:result, {"word":params, "kind": m.kind, "menu": "  " . pathshorten(m.filename)} )
  endfor

  " 模糊匹配, menu='? name'
  let postfix = substitute(name, '^.*[.:]\(\k\+\)$', '\1', '')
  let pattern = postfix . '$'
  let list = taglist( pattern )
  call filter(list, " v:val.name =~ '\\<" . pattern . "' && v:val.kind == 'f' " )
  for m in list 
    let params = substitute(m.cmd, '^.*\((.*)\).*$', '\1', '')
    call add( a:result, {"word":params, "kind": m.kind, "menu": "? " . m.name } )
  endfor
endfun

" 标识符补全
fun! s:addIdentifier( result, base )
  let flags = {}
  " 精确匹配
  let pattern = '^' . escape(a:base, '.:') . '\k*$' " 用 ^$ 精确匹配 tags
  let list = taglist( pattern )
  if a:base !~ '[.:]'   " 如果 base 中是简单标识符, 则不可能是属性引用, 因此过滤掉 m
    call filter(list, " v:val.kind !=? 'm' " )
  endif
  for m in list
    let postfix = substitute(m.name, '^.*[.:]\(\k\+\)$', '\1', '') " 提取后面的关键字序列, 'def' 
    let flags[postfix] = 1
    call add( a:result, s:getSignature(m, '') )
  endfor
  " 系统关键字
  call s:addSystemKeywords( a:result, a:base )
  " 模糊匹配
  if a:base =~ '\k[.:]\k' " 模糊匹配需要包含 prefix/postfix 两部分, 'abc.def' 
    let str = a:base
    let postfix = substitute(str, '^.*[.:]\(\k\+\)$', '\1', '') " 提取后面的关键字序列, 'def' 
    let prefix = str[0: -strlen(postfix)-1 ]   " 前面的内容 'abc.' 
    let pattern = postfix . '\k*$'      " 匹配后面 'def\k*$' 
    let list = taglist( pattern )
    call filter(list, " v:val.name =~ '\\<" . pattern . "' && v:val.kind != 'd' " )  " 匹配 \<, 且不是定义
    for m in list
      let postfix = substitute(m.name, '^.*[.:]\(\k\+\)$', '\1', '') " 提取后面的关键字序列, 'def' 
      if !exists('flags[postfix]')
        call add(a:result, s:getSignature(m, prefix) )
      endif
    endfor
  endif
  " isa 匹配
  if len(a:result) < 2 && a:base =~ '\k\.$'
    let id = substitute(a:base, '^.*\<\(\k\+\)\.$', '\1', '')
    let type = s:getIsaType( id )
    if len(type)
      let pattern = '^' . type . '[.:].*$'
      let list = taglist(pattern)
      for m in list
        call add( a:result, s:getSignature(m, a:base ) )
      endfor
    endif
  endif
endfun

" 获取 isa 类型
fun! s:getIsaType( id )
  let lineid = line('.') - 1
  let use_isa = 1
  while lineid > 0 
    let line = getline(lineid)
    if line =~ '^\s*$'
      let use_isa = 0
    elseif use_isa && line =~ '\ isa\ '
      let type = substitute(line, '^.*\<' . a:id . '\ isa\ \(\k\+\)\>.*$', '\1', '')
      if type =~ '^\k\+$'
        return type
      endif
    elseif line =~ '\ ISA\ '
      let type = substitute(line, '^.*\<' . a:id . '\ ISA\ \(\k\+\)\>.*$', '\1', '')
      if type =~ '^\k\+$'
        return type
      endif
    endif
    let lineid -= 1
  endwhile
  return ""
endfun

" 获取签名
fun! s:getSignature( m, prefix )
  " 模糊匹配, menu='? name'
  if strlen(a:prefix)
    let str = a:m.name
    let postfix = substitute(str, '^.*[.:]\(\k\+\)$', '\1', '') " 提取后面的关键字序列, 'def' 
    let mname = a:prefix . postfix
    let menu = '? ' . a:m.name
    return { "word": mname, "menu": menu, "kind":a:m.kind }
  " 精确匹配, menu='filename'
  else
    let mname = a:m.name
    let menu = pathshorten(a:m.filename)
    return { "word": mname, "menu": menu, "kind":a:m.kind }
  endif
endfun

" 获取系统补全, 从 xolox 插件中获取功能(xolox#lua#completefunc)
fun! s:addSystemKeywords( result, base )
  " 把所有可能的选项, 都加入到 items 中
  let items = []
  if xolox#misc#option#get('lua_complete_keywords', 1)
    call extend(items, g:xolox#lua_data#keywords)
  endif
  if xolox#misc#option#get('lua_complete_globals', 1)
    call extend(items, g:xolox#lua_data#globals)
  endif
  if xolox#misc#option#get('lua_complete_library', 1)
    call extend(items, g:xolox#lua_data#library)
  endif
  " 构造模版, 过滤结果保存在 items 中
  let pattern = '^' . xolox#misc#escape#pattern(a:base)
  call filter(items, 'v:val.word =~ pattern')
  " 添加原型描述
  for entry in items
    let signature = xolox#lua#getsignature(entry.word)
    if !empty(signature) && signature != entry.word
      let entry.menu = signature
    endif
  endfor
  " 添加到现有列表中
  call extend(a:result, items)
endfun

" 检查是否可以补全
fun! hproof#luacomp#CompleteDynamic(type)  
  let line = getline('.')
  let start = col('.') - 1
  if line[ start-1 ] =~ '\k'
    return a:type . "\<c-x>\<c-u>"
  endif
  return a:type
endfun

" 设置宏, 输入 . : ( <c-j> 后启动补全
fun! hproof#luacomp#SetupMaps()

  " ctags
  noremap <f5> :!luatags<cr>

  " gp, gn, gN, gc, <c-]>
  noremap <silent> gp :call hproof#luacomp#PreviewTag(0)<cr>
  noremap <silent> <c-]> :call hproof#luacomp#PreviewTag(1)<cr>
  noremap <silent> gn :call hproof#luacomp#PreviewTagOffset(1)<cr>
  noremap <silent> gN :call hproof#luacomp#PreviewTagOffset(-1)<cr>
  noremap <silent> gc :pclose<cr>

  " complete
  inoremap <silent> <buffer> <expr> : hproof#luacomp#CompleteDynamic(':')
  inoremap <silent> <buffer> <expr> . hproof#luacomp#CompleteDynamic('.')
  inoremap <silent> <buffer> <expr> ( hproof#luacomp#CompleteDynamic('(')
  inoremap <silent> <buffer> <c-j> <c-x><c-u>
  set completefunc=hproof#luacomp#CompleteFunc
endfun
command! LuaSetup call hproof#luacomp#SetupMaps()

" 预览光标下的单词
fun! hproof#luacomp#PreviewTag( is_jump )
  let result = []
  " 获得最长关键字, 精确匹配
  let word1 = s:getCurrentTag(0)
  if len(word1)
    let list1 = taglist('^' . word1 )
    call extend(result, list1)
    " isa 匹配, abc.def
    let pattern = '^\(\k\+\)\([.:]\)\(\k\+\)$'
    if word1 =~ pattern
      let id = substitute(word1, pattern, '\1', '')
      let type = s:getIsaType( id )
      if len(type)
        let word1 = substitute(word1, pattern, type . '\2\3', '')
        let list1 = taglist('^' . word1 )
        call extend(result, list1)
      endif
    endif
  endif
  " 获得模糊匹配
  let word2 = s:getCurrentTag(1)
  if len(word2)
    let list2 = taglist(word2)
    let pattern = '\<' . word2 . '\>'
    call filter(list2, 'v:val.name =~ pattern')
    call extend(result, list2)
  endif
  " ok
  if len(result)
    let s:prev_list = result
    let s:prev_idx = 0
    if a:is_jump
      exe 'tag ' . result[0].name
    else
      call hproof#luacomp#PreviewTagOffset(0)
    endif
  else
    let s:prev_list = []
    let s:prev_idx = 0
  endif
endfun

fun! hproof#luacomp#PreviewTagOffset( off )
  if len(s:prev_list)
    let s:prev_idx = (s:prev_idx + a:off) % len(s:prev_list) 
    let m = s:prev_list[s:prev_idx]
    exe 'silent! ptag ' . m.name
    echo (s:prev_idx+1) . '/' . len(s:prev_list) m.name pathshorten(m.filename)
  endif
endfun

" 获取光标下的字符, abc.def:foo
fun! s:getCurrentTag( one_word )
  let line = getline('.')
  if strlen(line)
    let start = col('.') - 1
    let match1 = a:one_word? '\k' : '[.:]\|\k'
    if line[start] =~ match1
      " 寻找 p1
      let p1 = start
      while p1 > 0 && line[p1-1] =~ match1
        let p1 -= 1
      endwhile
      " 寻找 p2
      let p2 = start
      while line[p2+1] =~ '\k'
        let p2 += 1
      endwhile
      return line[p1 : p2]
    endif
  endif
  return ""
endfun
